﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Ue.Aop;

namespace Ue.Aop.Methods
{
    public class MethodWeaver : IWeaver
    {
        public MethodWeaver(ModuleWeaver moduleWeaver)
        {
            this.ModuleWeaver = moduleWeaver;
        }

        public ModuleWeaver ModuleWeaver { get; private set; }

        public void Execute()
        {
            var markerTypeDefinitions = this.FindMarkerTypes();

            var decorator = new MethodDecorator(this.ModuleWeaver.ModuleDefinition);

            var methods = this.FindAttributedMethods(markerTypeDefinitions);
            foreach (var method in methods)
                decorator.Decorate(method.Item1, method.Item2);
        }

        private IList<TypeDefinition> FindMarkerTypes()
        {
            var markerAssembly = this.ResolveConfigMarkerAssembly();

            var markerModules = markerAssembly == null
                ? new[] { this.ModuleWeaver.ModuleDefinition }
                : markerAssembly.Modules.ToArray();

            var markerTypeDefinitions = markerModules.SelectMany(module => module.Types)
                .Where(type => type.Name.Equals("IMethodDecorator") || type.Name.Equals("MethodDecoratorAttribute"))
                .ToList();

            if (!markerTypeDefinitions.Any())
                throw new WeavingException("Could not find type 'IMethodDecorator' or 'MethodDecoratorAttribute'");

            if (!markerTypeDefinitions.Any(HasCorrectMethods))
                throw new WeavingException("IMethodDecorator does not contain correct OnEntry, OnExit and OnException methods");

            return markerTypeDefinitions;
        }

        private AssemblyDefinition ResolveConfigMarkerAssembly()
        {
            var methodDecoratorConfig = this.ModuleWeaver.Config.Element("MethodDecorator");
            if (methodDecoratorConfig == null)
            {
                return null;
            }

            var markerAssemblyAttribute = methodDecoratorConfig.Attribute("markerAssembly");
            if (markerAssemblyAttribute == null)
            {
                return null;
            }

            try
            {
                return this.ModuleWeaver.AssemblyResolver.Resolve(markerAssemblyAttribute.Value);
            }
            catch
            {
                return null;
            }
        }

        private static bool HasCorrectMethods(TypeDefinition type)
        {
            return type.Methods.Any(IsOnEntryMethod) && type.Methods.Any(IsOnExitMethod) && type.Methods.Any(IsOnExceptionMethod);
        }

        private static bool IsOnEntryMethod(MethodDefinition m)
        {
            return m.Name == "OnEntry" && m.Parameters.Count == 2 && m.Parameters[1].ParameterType.FullName == "System.Reflection.MethodBase";
        }

        private static bool IsOnExitMethod(MethodDefinition m)
        {
            return m.Name == "OnExit" && m.Parameters.Count == 3 && m.Parameters[1].ParameterType.FullName == "System.Reflection.MethodBase";

        }

        private static bool IsOnExceptionMethod(MethodDefinition m)
        {
            return m.Name == "OnException" && m.Parameters.Count == 3
                && m.Parameters[1].ParameterType.FullName == "System.Reflection.MethodBase"
                && m.Parameters[2].ParameterType.FullName == "System.Exception";
        }

        private IEnumerable<Tuple<MethodDefinition, CustomAttribute>> FindAttributedMethods(IEnumerable<TypeDefinition> markerTypeDefintions)
        {
            return this.ModuleWeaver.ModuleDefinition
                .Types
                .SelectMany(type => type.GetAllTypes())
                .SelectMany(type =>{
                    return type.GetMethods(markerTypeDefintions.ToArray());
                })
                .Select(item => Tuple.Create(item.Item1, item.Item2.FirstOrDefault()));
        }
    }
}